/*
工具箱
*/

let UID = 1

/**
 * 逐增ID
 */
function getId () {
  return UID++
}

/**
 * 判断有值
 * @param {Any} v
 * @returns {Boolean} 是true，否false
 */
function isVal (v) {
  return !(v === undefined || v === null)
}

/**
 * 是否相等
 * @param {Any} a
 * @param {Any} b
 * @returns {String}
 */
function isEqual (a, b) {
  // eslint-disable-next-line
  return a === b || a == b
}

/**
 * 获取精确的数据类型
 * typeof Symbol() -> symbol
   toString.call(Symbol()) -> [object Symbol]
   toString未使用call方法，this始终指向Object: toString(1)->"[object Object]"
   toString.call(document.getElementsByTagName('body'))->"[object HTMLCollection]"
 * @param {Any} v
 * @returns {String}
 */
function toType (v) {
  return Object.prototype.toString.call(v).replace(/\[|\]/g, '').split(/\s/)[1].toLocaleLowerCase()
}

/**
 * 判断布尔
 * @param {Boolean} v
 * @returns {Boolean} 是true，否false
 */
function isBoolean (v) {
  return typeof v === 'boolean'
}

/**
 * 判断字符串
 * @param {String} v
 * @returns {Boolean} 是true，否false
 */
function isString (v) {
  return typeof v === 'string'
}

/**
 * 判断数字
 * isNaN 非数字，返回true
 * toString.call(NaN) : number
 * typeof NaN : number
 * @param {Any} v
 * @returns {Boolean} 是true，否false
 */
function isNumber (v) {
  return !isNaN(v)
}

/**
 * 判断对象
 * @param {Any} v
 * @returns {Boolean} 是true，否false
 */
function isObject (v) {
  return toType(v) === 'object'
}

/**
 * 判断日期对象
 * @param {Object} v
 * @returns {Boolean} 是true，否false
 */
function isDate (v) {
  return toType(v) === 'date'
}

/**
 * 判断函数
 * @param {Object} v
 * @returns {Boolean} 是true，否false
 */
function isFunction (v) {
  return typeof v === 'function'
}

/**
 * 判断Promise
 * @param {Object} v
 * @returns {Boolean} 是true，否false
 */
function isPromise (v) {
  return isObject(v) && isFunction(v.then)
}

/**
 * 判断window
 * @param {Any} v
 * @returns {Boolean} 是true，否false
 */
function isWindow (v) {
  return toType(v) === 'window'
}

/**
 * 判断 document
 * @param {Any} v
 * @returns {Boolean} 是true，否false
 */
function isDocument (v) {
  return Object.prototype.toString.call(v) === '[object HTMLDocument]'
}

/**
 * 判断dom节点
 * @param {Any} v
 * @returns {Boolean} 是true，否false
 */
function isDom (v) {
  return typeof HTMLElement !== 'undefined'
    ? v instanceof HTMLElement
    : v && typeof v === 'object' && (v.nodeType === 1 || isVal(v.nodeType))
}

/**
 * 判断为空
 * @param {String|Array|Object} ob
 * @returns {Boolean} 是true，否false
 */
function isEmpty (v) {
  if (Array.isArray(v) || typeof v === 'string') {
    return v.length === 0
  }
  if (v instanceof Map || v instanceof Set) {
    return v.size === 0
  }
  if (isObject(v)) {
    return Object.keys(v).length === 0
  }
  return false
}

/**
 * 检查是否是可枚举属性
 * @param {Object} o
 * @param {String} k
 * @returns {Boolean} 是true，否false
 */
function hasProp (o, k) {
  return Object.prototype.hasOwnProperty.call(o, k)
}

/**
 * 随机字符串
 * @param {Number} range
 * @returns {String}
 */
const CHARACTER = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
const CHARACTER_LENGTH = CHARACTER.length
function anystr (range) {
  range = range || 8
  const r = []
  for (let i = 0; i < range; i++) {
    r.push(CHARACTER_LENGTH.charAt(
      Math.floor(Math.random() * length)
    ))
  }
  return r.join('')
}

/**
 * 模板字符串替换
 * @param {arguments} any
 * @returns {String}
 */
function format () {
  let i = 0
  const args = arguments
  const length = args.length
  return String(args[0]).replace(/%[sdj]/g, function (x) {
    if (i >= length) {
      return x
    }
    i++
    switch (x) {
      case '%s':
        return String(args[i])
      case '%d':
        return Number(args[i])
      case '%j':
        return json2Str(args[i])
      default:
        return x
    }
  })
}

/**
 * 去掉前后空格
 * @param {String} v
 * @returns {String}
 */
function trim (v) {
  return String(v).replace(/(^\s*)|(\s*$)/g, '')
}

/**
 * 去掉前空格
 * @param {String} v
 * @returns {String}
 */
function trimStart (v) {
  return String(v).replace(/^\s*/, '')
}

/**
 * 去掉后空格
 * @param {String} v
 * @returns {String}
 */
function trimEnd (v) {
  return String(v).replace(/\s*$/, '')
}

/**
 * 查找指定字符串
 * String(ls)：将其转换为字符串类
 * @param {Array} ls
 * @param {String|Number} v
 * @returns {String}
 */
function findStr (ls, v) {
  return (!isVal(ls) || !isVal(v))
    ? false
    : new RegExp(',' + String(v) + ',').test(',' + String(ls) + ',')
}

/**
 * 单词首字母大写
 * @param {String} v
 * @returns {String}
 */
function capitalize (v) {
  return String(v).toLowerCase().replace(/( |^)[a-z]/g, function (s) {
    return s.toUpperCase()
  })
}

/**
 * 中文Unicode编码
 * @param {String} v
 * @returns {String}
 */
function unicodeEncode (v) {
  if (!isVal(v)) {
    return ''
  }
  v = String(v)
  const r = []
  for (let i = 0, length = v.length; i < length; i++) {
    // charCodeAt() 方法返回0到65535之间的整数，表示给定索引处的UTF-16代码单元
    // slice(-4): 编码后字符不满足4位的用0填充，并截取最后四位
    r.push(
      ('00' + v.charCodeAt(i).toString(16)).slice(-4)
    )
  }
  return '\\u' + r.join('\\u')
}

/**
 * 中文Unicode解码
 * @param {String} v
 * @returns {String}
 */
function unicodeDecode (v) {
  return !v ? '' : unescape(v.replace(/\\/g, '%'))
}

/**
 * 返回一个新数组
   可以用来将一个类数组（Array-like）对象/集合转换成一个新数组
 * @param {Any} v
 * @returns {Array}
 */
function toArray (v) {
  return !v ? [] : [].slice.call(v)
}

/**
 * 返回一个新函数
 * @param {String} fn
 * @returns {Function}
 */
function toFunction (fn) {
  const Fun = Function // 一个变量指向Function，防止有些前端编译工具报错
  return typeof fn === 'string' ? new Fun('return ' + fn)() : fn
}

/**
 * JSON对象序列化
 * @param {Object} v
 * @returns {String}
 */
function json2Str (v) {
  return (isObject(v) || Array.isArray(v)) ? JSON.stringify(v) : v
}

/**
 * 转换为JSON对象
 * @param {String} fn
 * @returns {Object}
 */
function toJson (fn) {
  return typeof fn === 'string' ? toFunction(fn) : fn
}

/**
 * json对象转参数字符串
 * @param {Object} o
 * @returns {String}
 */
function json2Param (o) {
  return isObject(o)
    ? Object.keys(o).map(k => (`${k}=${json2Str(o[k])}`)).join('&')
    : ''
}

/**
 * 参数字符串转json对象
   toJson('{"' + search.replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') + '"}') 这种处理不了有json的数据
 * @param {String} ob
 * @returns {Object}
 */
function param2Json (url) {
  if (!isVal(url) || !isString(url)) {
    return {}
  }
  url = decodeURIComponent(url)
  const search = url.substr(url.indexOf('?') + 1, url.length)
  const r = {}
  search.split('&').forEach(s => {
    const [a, b] = s ? s.split('=') : []
    a && b ? (r[a] = toJson(b)) : ''
  })
  return r
}

/**
 * 遍历数组、对象
 * @param {Array|Object} ob
 * @param {Function} fn
 */
function forEach (ob, fn) {
  if (!fn) {
    return
  }
  let o
  if (Array.isArray(ob)) {
    const length = ob.length
    for (let i = 0; i < length; i++) {
      o = ob[i]
      fn.call(o, o, i, ob)
    }
  } else if (isObject(ob)) {
    for (const i in ob) {
      o = ob[i]
      fn.call(o, o, i, ob)
    }
  }
}

/**
 * 浅拷贝对象
 * @param {arguments}
 * @returns {Object}
 * @eg objectMerge({},{},...)
 */
function objectMerge () {
  const args = [].slice.call(arguments)
  const target = Object(args.shift())
  args.forEach((item) => {
    if (isObject(item)) {
      Object.keys(item).forEach(key => {
        isVal(item[key]) ? target[key] = item[key] : ''
      })
    }
  })
  return target
}

/**
 * 深度克隆对象
 * @param {arguments}
 * @returns {Object}
 * @eg deepClone({},{},...)
 */
function deepClone () {
  const args = [].slice.call(arguments)
  const target = Object(args.shift())
  args.forEach((element) => {
    if (!isObject(element)) {
      return
    }
    Object.keys(element).forEach((key) => {
      // 防止对象原型被污染，防止永无休止的循环
      if (key === '__proto__') {
        return
      }
      const row = element[key]
      if (!isVal(row)) {
        return
      }
      if (isObject(row)) {
        target[key] = deepClone(target[key], row)
      } else if (Array.isArray(row)) {
        if (!Array.isArray(target[key])) {
          target[key] = []
        }
        row.forEach((col, index) => {
          if (isObject(col)) {
            target[key][index] = deepClone(target[key][index], col)
          } else {
            target[key][index] = col
          }
        })
      } else {
        target[key] = row
      }
    })
  })
  return target
}

/**
 * Object格式化
 * @param {Object} o
 * @return {Object}
 */
function objectFormat (o) {
  return JSON.parse(JSON.stringify(o))
}

/**
 * 获取url中的参数
 * @param {String} name 参数名字
 * @param {String} url url地址
 * @returns {String}
 */
function urlParam (name, url) {
  /*
  let str
  if (!url) {
    str = decodeURIComponent(location.search).substr(1)
  } else {
    str = decodeURIComponent(url.split('?'))[1]
  }
  const reg = new RegExp('&\\s*' + name + '\\s*=\\s*([^&]*)&', 'gi')
  const v = reg.exec('&' + str + '&')
  return !v ? '' : trim(v[1])
  */
  return param2Json(url || location.search)[name]
}

/**
* 会话缓存
* @param {String} name 键
* @param {any} v 值
* @returns {null|String} 值
*/
function sessionCache (name, v) {
  return isVal(v) ? sessionStorage.setItem(name, json2Str(v)) : sessionStorage.getItem(name)
}

/**
* 本地储存
* @param {String} name 键
* @param {any} v 值
* @returns {null|String} 值
*/
function localCache (name, v) {
  return isVal(v) ? localStorage.setItem(name, json2Str(v)) : localStorage.getItem(name)
}

export {
  isVal,
  isEqual,
  toType,
  isBoolean,
  isString,
  isNumber,
  isObject,
  isDate,
  isFunction,
  isPromise,
  isWindow,
  isDocument,
  isDom,
  isEmpty,
  hasProp,

  anystr,
  getId,
  format,
  trim,
  trimStart,
  trimEnd,
  findStr,
  capitalize,
  unicodeEncode,
  unicodeDecode,

  toArray,
  toFunction,
  json2Str,
  toJson,
  json2Param,
  param2Json,
  forEach,
  objectMerge,
  deepClone,
  objectFormat,

  urlParam,
  sessionCache,
  localCache
}
